import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;


/**
 * What does this class do:
 * Neurons compute an "output value" based on (weighted) "input values".
 * These "input values" are...
 * ...either "output values" from other Neurons TODO of????
 * ...or a value that is manually given.
 * 
 * In which way are Neurons connected to a neuronal network:
 * Neurons maintain/know from which Neurons they get inputs (and the weights)
 * but they DO NOT maintain/know which Neurons get their output value.
 * (The "output value" can be requested by {@link #getOutput()}.)
 * 
 * Output value:
 * The "output value" has to be recomputed manually (!) every time one of the
 * input Neurons changes. (The activation function is a sigmoid.)
 * 
 * Updating the weights (for backpropagation):
 * The weights of this Neuron can be updated by calling one of the
 * updateWeights... methods.
 * 
 * Learning rate:
 * The initial learning rate η is 0.5. The learning rate can be set manually.
 * It is recommended to start a neuronal network with a higher learning rate
 * (e.g. η = 0.7) and to decrease it (manually) over time.
 * 
 * (Attention: In this context/class the term "input Neurons" refers to the
 * predecessor Neurons from which this Neuron gets "input values".
 * Actually the term "input Neurons" refers to Neurons which get their
 * "input values" directly without any former Neurons – this is NOT meant here!)
 * 
 * @author 9grenzin
 * @version 25th January 2013
 */
public class Neuron
{
   // A Map of the Neurons (and their weights) from which this Neurons gets its
   // "input values":
   private HashMap<Neuron, Double> _inNeurons = new HashMap<Neuron, Double>();
   // The computed "output value" of this Neuron:
   private double _output = 0;
   private double _learningRate = 0.5;
   // Needed for backpropagation for hidden Neurons, is computed from the
   // successors Neurons of this Neuron:
   private double _sumOfWeightedSuccessorDeltas = 0;

   /**
    * default constructor
    */
   public Neuron()
   {
   }

   /**
    * 
    * @param inNeurons
    *           Initializes the Neuron with the specified input Neurons and
    *           their weights.
    */
   public Neuron(HashMap<Neuron, Double> inNeurons)
   {
      addInputNeurons(inNeurons);
   }

   /**
    * @param neuron
    *           The Neuron to be added to the input Neurons.
    * @param initWeight
    *           The initial weight for "input values" from Neuron.
    */
   public void addInputNeuron(Neuron neuron, Double initWeight)
   {
      _inNeurons.put(neuron, initWeight);
   }

   /**
    * @param inNeurons
    *           Same as {@link #addInputNeuron(Neuron, Double)} but with
    *           multiple Neurons.
    */
   public void addInputNeurons(HashMap<Neuron, Double> inNeurons)
   {
      _inNeurons.putAll(inNeurons);
   }

   /**
    * @param neuron
    * @return The weight associated with the Neuron or null if the Neuron wasn't
    *         an input Neuron.
    */
   public Double removeInputNeuron(Neuron neuron)
   {
      return _inNeurons.remove(neuron);
   }

   /**
    * @param learningRate
    *           The new learning rate for this Neuron.
    */
   public void setLearningRate(double learningRate)
   {
      _learningRate = learningRate;
   }

   /**
    * Computes a new "output value" that can be requested by the
    * {@link #getOutput()} method.
    * The activiation function is a sigmoid with β = 1:
    * output = 1 / ( 1 + e^(-SumOfWeightedInputs) ).
    */
   public void updateOutput()
   {
      double sumOfWeightedInputs = 0;

      // Iterate over all input Neurons and sum their outputs * weights
      for (Entry<Neuron, Double> entry : _inNeurons.entrySet())
      {
         Neuron neuron = entry.getKey();
         Double weight = entry.getValue();

         sumOfWeightedInputs += neuron.getOutput() * weight;
      }

      // Compute sigmoid.
      _output = 1 / (1 + Math.exp(sumOfWeightedInputs));
   }

   /**
    * Sets the "output value" manually to the given parameter.
    * Especially needed to use a Neuron as an (real) "input Neuron" of a
    * neuronal net.
    * 
    * @param inputValue
    *           The parameter is called "input..." because it is like the Neuron
    *           would compute the new "output value" by getting this parameter
    *           as an "input value" and weighting it with 1.0.
    */
   public void setOutput(Double inputValue)
   {
      _output = inputValue;
   }

   /**
    * Returns the last computed "output value".
    * (A new "output value" is computed by {@link #updateOutput()}.)
    */
   public double getOutput()
   {
      return _output;
   }

   /**
    * If this Neuron is an output Neuron (or if you know the exact desired
    * output of this Neuron) call this method to update the weights.
    * 
    * @param desiredOutput
    *           The output the Neuron was supposed to compute.
    */
   public void updateWeights_outputNeuron(double desiredOutput)
   {
      // Compute delta for an output Neuron:
      double delta = (_output - desiredOutput) * _output * (1.0 - _output);
      updateWeights(delta);
   }

   /**
    * If this Neuron is a hidden Neuron call this method to update the weights.
    * 
    * (To update the weights of a hidden Neuron properly the Neuron needs the
    * weighted deltas of all successor Neurons.
    * Therefore it is necessary that the successor Neurons call
    * {@link #addWeightedSuccessorDelta(double)}!)
    */
   public void updateWeights_hiddenNeuron()
   {
      // Compute delta for a hidden Neuron:
      double delta = _sumOfWeightedSuccessorDeltas * _output * (1.0 - _output);
      updateWeights(delta);
   }

   /**
    * The part of updating the weights that is equal for hidden & output
    * Neurons.
    */
   private void updateWeights(double delta)
   {
      HashMap<Neuron, Double> updatedInNeuronsMap = new HashMap<Neuron, Double>();

      for (Entry<Neuron, Double> entry : _inNeurons.entrySet())
      {
         Neuron neuron = entry.getKey();
         Double weight = entry.getValue();

         // Tell "input Neurons" of this Neuron about the delta, so that they
         // can update their weights properly.
         neuron.addWeightedSuccessorDelta(delta * weight);

         // Compute & set new weights
         Double updatedWeight = weight - (_learningRate * delta * neuron.getOutput());
         updatedInNeuronsMap.put(neuron, updatedWeight);
      }

      _inNeurons = updatedInNeuronsMap;
      _sumOfWeightedSuccessorDeltas = 0; // set it to 0 for the next
                                         // backpropagation run.
   }

   /**
    * Adds the weighted delta of a successor Neuron to this Neuron.
    * 
    * (To update the weights of a hidden Neuron properly the hidden Neuron needs
    * to know the deltas of its successor Neurons.
    * But since Neurons do NOT know their successor Neurons it is necessary that
    * the successor Neurons pass their deltas to hidden Neurons themselves which
    * can be done by calling this method.)
    * 
    * @param weightedDelta
    *           The delta of a successor Neuron multiplied by the weight between
    *           this Neuron and the successor Neuron.
    */
   public void addWeightedSuccessorDelta(double weightedDelta)
   {
      _sumOfWeightedSuccessorDeltas += weightedDelta;
   }
}
